#  /**
#  * Copyright (c) 2022 Beijing Jiaotong University
#  * PhotLab is licensed under [Open Source License].
#  * You can use this software according to the terms and conditions of the [Open Source License].
#  * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
#  *
#  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
#  * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
#  * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#  *
#  * See the [Open Source License] for more details.
#  */
#  /**
#  * Author: Zheng Wang
#  * Created: Jun. 27, 2022
#  * Supported by: National Key Research and Development Program of China
#  */

import time
from phot_engine.globals import VerificationError
from phot_engine.utils import universal_function as uf
from phot_engine.computing_graph.vertex import Vertex
from phot_engine.computing_graph.parameter import Parameter
from phot_engine.computing_graph.edge import Edge
from phot_engine.computing_graph.port import Port
from phot_engine.computing_graph.phot_lab_graph import PhotLabGraph
from phot_engine.computing_graph.task_global_parameter import TaskGlobalParameter
from phot_engine.globals.task_priority import TaskPriority
from phot_engine.globals.task_status import TaskStatus
from phot_engine.core.simulation_task import SimulationTask
import logging

# used to check edge key
EDGE_KEY_CHECK_LIST = ["sourceVertexId",
                       "sourcePortId",
                       "destinationVertexId",
                       "destinationPortId",
                       "signalType"]


def _verify_vertex_data(to_verify_vertex_data, scrutiny=False):
    """
        verify vertex data, it should be a dict type
    :param to_verify_vertex_data:
    :return:
    """
    if isinstance(to_verify_vertex_data, dict):
        return True
    else:
        return False


def _verify_edge_data(to_verify_edge_data, scrutiny=False):
    """
        verify edge_data,it should be a dict type.The intensity of scrutiny is determined by variables-scrutiny
    :param to_verify_edge_data:
    :param scrutiny: If true,function will check to_verify_edge_data's key one by one.
                     When both the number of key and key name are legal,then function returns true.
    :return:
    """

    if isinstance(to_verify_edge_data, dict):
        if not scrutiny:
            return True
        else:
            to_verify_edge_data_dict_keys = list(to_verify_edge_data.keys())
            cnt = 0
            for elem in EDGE_KEY_CHECK_LIST:
                if uf.check_element_existence_in_list(elem, to_verify_edge_data_dict_keys):
                    cnt += 1
                else:
                    return False
            if cnt == len(EDGE_KEY_CHECK_LIST):
                return True
            else:
                return False
    else:
        return False


def _verify_global_data(to_verify_global_data, scrutiny=False):
    """
        verify system global parameter dictionary
    :param to_verify_global_data:
    :param scrutiny:
    :return:
    """
    if isinstance(to_verify_global_data, list):
        return True
    else:
        return False


class GraphEntity:
    """
        A graph entity corresponds to a simulation system in the GUI.
        graph entity includes these three significant data structure:
            1. __vertex_data_dict: dict type,key is components' name,value is a dict which includes vertex meta data
            2. __edge_data_list:list type,to contain all edge in the GUI
            3. __global_data_dict:dict type,key is global parameter name,value is global parameter's meta data
            图形实体对应GUI中的模拟系统
    """

    def __init__(self, scrutiny=False):
        """
        :param scrutiny:When scrutiny sets to true, stricter parameter checking is enabled
        """
        self.__scrutiny = scrutiny
        self.__vertex_data_dict = dict()
        self.__edge_data_list = []
        self.__global_data_dict_list = []

    def add_vertex(self, vertex_data):
        """
            add a vertex into __vertex_data_dict
        :param vertex_data:
        :return:
        """
        if _verify_vertex_data(to_verify_vertex_data=vertex_data):
            vertex_id = vertex_data["componentId"]
            self.__vertex_data_dict[vertex_id] = vertex_data
            # logging.debug() 方法用于输出 DEBUG 级别的日志信息。
            logging.debug("add a vertex,vertex id:{} to graph entity.".format(vertex_id))
            return True
        else:
            raise VerificationError("vertex_data should be a dict type,not a {}".format(type(vertex_data)))

    def __verify_vertex_id_existence(self, to_check_vertex_id):
        """
            verify to_check_vertex_id is in __vertex_data_dict.keys() or not
        :param to_check_vertex_id: string type vertex id
        :return:
        """
        vertex_key_list = list(self.__vertex_data_dict.keys())
        if to_check_vertex_id in vertex_key_list:
            return True
        else:
            return False

    def delete_vertex(self, vertex_id):
        """
            delete vertex from graph entity by vertex id
        :param vertex_id:
        :return:
        """
        if self.__verify_vertex_id_existence(vertex_id):
            del self.__vertex_data_dict[vertex_id]
            logging.debug("Delete a vertex,vertex id:{}".format(vertex_id))
            return True
        else:
            raise KeyError("Vertex key: {} do not exist".format(vertex_id))

    def alter_vertex(self, vertex_id, vertex_data):
        if self.__verify_vertex_id_existence(vertex_id):
            if _verify_vertex_data(vertex_data, self.__scrutiny):
                self.__vertex_data_dict[vertex_id] = vertex_data
                logging.debug("Alter a vertex,vertex id:{},new value:{}".format(vertex_id, vertex_data))
                return True
            else:
                raise VerificationError("vertex_data should be a dict type,not a {}".format(type(vertex_data)))
        else:
            raise KeyError("Vertex key: {} do not exist".format(vertex_id))

    def __gen_part_of_edge_dict(self, edge_data):
        """
            generate __edge_data_dict's key and value by edge_data
        :param edge_data:
        :return:
        """
        if _verify_edge_data(edge_data, self.__scrutiny):
            edge_key_tuple = (edge_data["sourceVertexId"], edge_data["sourcePortId"])
            edge_value_tuple = (edge_data["destinationVertexId"],
                                edge_data["destinationPortId"],
                                (edge_data["signalType"]))
            return edge_key_tuple, edge_value_tuple
        else:
            raise VerificationError("edge_data can not pass function argument entry review")

    def add_edge(self, edge_data):
        """
            add an edge to self.__edge_data_list
        :param edge_data:
        :return:
        """
        if _verify_edge_data(edge_data, self.__scrutiny):
            self.__edge_data_list.append(edge_data)
            logging.debug("add an edge: {} to graph entity.".format(edge_data))
            return True
        else:
            raise VerificationError("edge_data can not pass function argument entry review")

    def alter_edge(self, old_edge_data, new_edge_data):
        """
            1. delete old_edge_data in self.__edge_data_list
            2. add new_edge_data in self.__edge_data_list
        :param old_edge_data:
        :param new_edge_data:
        :return:
        """
        if _verify_edge_data(old_edge_data, self.__scrutiny) and _verify_edge_data(new_edge_data, self.__scrutiny):
            if self.delete_edge(old_edge_data):
                self.add_edge(new_edge_data)
                logging.debug("Alter an edge's direction,source:{},destination:{}".format(old_edge_data,
                                                                                          new_edge_data))
                return True
        else:
            raise VerificationError("edge_data can not pass function argument entry review")

    def delete_edge(self, edge_data):
        """
            delete an edge in self.__edge_data_list
        :param edge_data:
        :return:
        """
        if _verify_edge_data(edge_data, self.__scrutiny):
            if edge_data in self.__edge_data_list:
                self.__edge_data_list.remove(edge_data)
                logging.debug("Delete an edge,old edge data:{}".format(edge_data))
                return True
            else:
                err_msg = "list.remove({}): {} not in list".format(edge_data, self.__edge_data_list)
                logging.error(err_msg)
                raise ValueError(err_msg)
        else:
            raise VerificationError("edge_data can not pass function argument entry review")

    def set_system_global_parameter(self, global_data_list:list):
        """
            to set system global parameter,global parameter should be unique.
            This function has two different ways to deal with global parameter.
                1. when the graph entity already have had a system global parameter
                2. when the graph entity already haven't had a system global parameter
        :param global_data_list:
        :return:
        """
        if global_data_list is None:
            self.__global_data_dict_list = []
            logging.warning("global data in graph entity is None")
            return True
        if _verify_global_data(global_data_list, scrutiny=self.__scrutiny):
            if 0 == len(self.__global_data_dict_list):
                self.__global_data_dict_list = global_data_list
            else:
                # 如果原来有全局变量，则用新的数据进行覆盖
                self.__global_data_dict_list.clear()
                self.__global_data_dict_list = global_data_list
            logging.debug("Set system global parameter list.")
            return True
        else:
            raise VerificationError("global_data expects to a list type,not a {}".format(type(global_data_list)))

    def __gen_parameter_list_from_dict_list(self, param_list) -> list:
        """
            Element in param_list is dictionary type,using this function to convert dict type to Parameter type
        :param param_list:
        :return: res_list:list type,element in the list is Parameter type
        """
        res_list = []
        for param_dict in param_list:
            param = Parameter(param_dict)
            res_list.append(param)
        return res_list

    def __gen_vertex_from_graph_entity(self, vertex_key) -> Vertex:
        """
            generate vertex from graph entity
        :param vertex_key:
        :return:
        """
        vertex_meta_data = self.__vertex_data_dict[vertex_key]
        vertex_id = vertex_key
        phot_lib_class_name = vertex_meta_data["ptName"]
        phot_lib_func_name = vertex_meta_data["calculationMethod"]
        parallel_computing_method = vertex_meta_data["parallelComputingMethod"]
        is_analyzer = vertex_meta_data["isAnalyzer"]
        param_list = self.__gen_parameter_list_from_dict_list(vertex_meta_data["paramList"])
        vertex = Vertex(vertex_id=vertex_id,
                        phot_lib_class_name=phot_lib_class_name,
                        phot_lib_func_name=phot_lib_func_name,
                        parallel_computing_method=parallel_computing_method,
                        is_analyzer=is_analyzer,
                        vertex_param_list=param_list)

        return vertex

    def __gen_vertex_dict_from_graph_entity(self):
        result_vertex_dict = dict()
        for vertex_key in self.__vertex_data_dict.keys():
            res_vertex = self.__gen_vertex_from_graph_entity(vertex_key=vertex_key)
            result_vertex_dict[vertex_key] = res_vertex
        return result_vertex_dict

    def __gen_edge_list_from_graph_entity(self):
        result_edge_list = []
        for edge_data in self.__edge_data_list:
            source_port = Port(edge_data["sourceVertexId"], edge_data["sourcePortId"])
            destination_port = Port(edge_data["destinationVertexId"], edge_data["destinationPortId"])
            one_edge = Edge(source_port, destination_port, edge_data["signalType"])
            result_edge_list.append(one_edge)
        return result_edge_list

    def __gen_global_parameter(self):
        """
            Generate simulation task global parameter from graph entity
        :return:
        """
        # result_global_parameter = None
        param_list = self.__gen_parameter_list_from_dict_list(self.__global_data_dict_list)
        result_global_parameter = TaskGlobalParameter(param_list)
        # print(result_global_parameter.get_global_parameter_dict())
        return result_global_parameter

    def __gen_phot_lab_graph(self, global_system_parameter: TaskGlobalParameter = None):
        """
            生成计算图
        :return:
        """
        vertex_dict = self.__gen_vertex_dict_from_graph_entity()
        edge_list = self.__gen_edge_list_from_graph_entity()
        if global_system_parameter is not None:
            # 为每个顶点都添加全局变量
            global_system_parameter_dict = global_system_parameter.get_global_parameter_dict()
            for vertex_key in vertex_dict.keys():
                vertex_dict[vertex_key].append_global_system_parameter(global_system_parameter_dict)
        phot_lab_graph = PhotLabGraph(vertex_dict, edge_list)
        logging.info("PhotLab graph has generated.")
        return phot_lab_graph

    def gen_simulation_task(self, task_id="Unknown", priority=TaskPriority.LOW, execution_time=None) -> SimulationTask:
        """
            生成仿真任务
        :param task_id:
        :param priority:
        :param execution_time:
        :return:
        """
        if execution_time is None:
            execution_time = time.time()
        global_parameter = self.__gen_global_parameter()
        graph = self.__gen_phot_lab_graph(global_system_parameter=global_parameter)
        simulation_task = SimulationTask(task_id=task_id,
                                         starting_executing_time=execution_time,
                                         priority=priority,
                                         graph=graph,
                                         global_parameter=global_parameter)
        simulation_task.set_task_status(TaskStatus.TASK_CREATING)
        logging.info("Simulation task has generated.")
        return simulation_task

    def print_graph_entity_meta_data(self, show_detail=False):
        data_statistic = {
            "vertex number: ": len(self.__vertex_data_dict),
            "edge number: ": len(self.__edge_data_list),
        }
        print(data_statistic)
        if show_detail:
            print("Global parameter:\n", self.__global_data_dict_list)
            print("Vertex Info:\n", self.__vertex_data_dict)
            print("Edge Info:\n", self.__edge_data_list)
        return data_statistic
